/**
 * 
 */
package com.jdy.haoduoai.view;

/**
 * @author zp
 * 2016-12-5 ����5:35:59
 */

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.os.Build;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.ViewCompat;
import android.text.TextPaint;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.animation.LinearInterpolator;
import android.widget.OverScroller;

import com.ycsj.goldmedalnewconcept.R;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

//import com.jdy.haoduoai.R;

/**
 * Created by Raquib-ul-Alam Kanak on 7/21/2014. Website:
 * http://alamkanak.github.io/
 */
public class WeekView extends View {

  private enum Direction {
    NONE, LEFT, RIGHT, VERTICAL
  }

  protected int WIDTH;

  protected int HEIGHT;

  @Deprecated
  public static final int LENGTH_SHORT = 1;
  @Deprecated
  public static final int LENGTH_LONG = 2;
  private final Context mContext;
  private Paint mTimeTextPaint;
  private float mTimeTextWidth;
  private float mTimeTextHeight;
  private Paint mHeaderTextPaint;
  private float mHeaderTextHeight;
  private float mHeaderTextWidth;
  private float mHeaderHeight;
  public GestureDetectorCompat mGestureDetector;
  private OverScroller mScroller;
  private final PointF mCurrentOrigin = new PointF(0f, 0f);
  private Direction mCurrentScrollDirection = Direction.NONE;
  private Paint mHeaderBackgroundPaint;
  private Paint mCircleBackgroundPaint;
  private Paint mSelectedBackgroundPaint;
  private float mWidthPerDay;
  private Paint mDayBackgroundPaint;
  private Paint mHourSeparatorPaint;
  private float mHeaderMarginBottom;
  private Paint mTodayBackgroundPaint;
  private Paint mFutureBackgroundPaint;
  private Paint mPastBackgroundPaint;
  private Paint mFutureWeekendBackgroundPaint;
  private Paint mPastWeekendBackgroundPaint;
  private Paint mNowLinePaint;
  private Paint mTodayHeaderTextPaint;
  private Paint mEventBackgroundPaint;
  private float mHeaderColumnWidth;
  private List<EventRect> mEventRects;
  private List<? extends WeekViewEvent> mPreviousPeriodEvents;
  private List<? extends WeekViewEvent> mCurrentPeriodEvents;
  private List<? extends WeekViewEvent> mNextPeriodEvents;
  private TextPaint mEventTextPaint;
  private Paint mHeaderColumnBackgroundPaint;
  private final int mFetchedPeriod = -1; // the middle period the calendar has
  // fetched.
  private boolean mRefreshEvents = false;
  private Direction mCurrentFlingDirection = Direction.NONE;
  private boolean mIsZooming;
  private Calendar mFirstVisibleDay;
  private Calendar mLastVisibleDay;
  private boolean mShowFirstDayOfWeekFirst = true;
  private int mDefaultEventColor;
  private int mMinimumFlingVelocity = 0;
  private int mScaledTouchSlop = 0;
  // Attributes and their default values.
  // private int mHourHeight = 50;
  // private int mNewHourHeight = -1;
  // private int mMinHourHeight = 0; // no minimum specified (will be dynamic,
  // // based on screen)
  // private int mEffectiveMinHourHeight = mMinHourHeight; // compensates for
  // the
  // fact that you can't
  // keep zooming out.
  // private int mMaxHourHeight = 250;
  private int mColumnGap = 10;
  private int mFirstDayOfWeek = Calendar.SUNDAY;
  private int mTextSize = 12;
  private int mHeaderColumnPadding = 10;
  private int mHeaderColumnTextColor = Color.BLACK;
  private int mNumberOfVisibleDays = 3;
  private int mHeaderRowPadding = 10;
  private float mMondayGap = 80.0f;
  private float mYearMonthGap = 5.0f;
  private int mHeaderRowBackgroundColor = Color.WHITE;
  private int mDayBackgroundColor = Color.rgb(245, 245, 245);
  private int mPastBackgroundColor = Color.rgb(227, 227, 227);
  private int mFutureBackgroundColor = Color.rgb(245, 245, 245);
  private int mPastWeekendBackgroundColor = 0;
  private int mFutureWeekendBackgroundColor = 0;
  private int mNowLineColor = Color.rgb(102, 102, 102);
  private int mNowLineThickness = 5;
  private int mHourSeparatorColor = Color.rgb(230, 230, 230);
  private int mTodayBackgroundColor = Color.rgb(239, 247, 254);
  private int mHourSeparatorHeight = 2;
  private int mTodayHeaderTextColor = Color.rgb(39, 137, 228);
  private int mEventTextSize = 12;
  private int mEventTextColor = Color.BLACK;
  private int mEventPadding = 8;
  private int mHeaderColumnBackgroundColor = Color.WHITE;
  private boolean mIsFirstDraw = true;
  private boolean mAreDimensionsInvalid = true;
  private boolean mAreWeekHeaderEventInvalid = true;
  @Deprecated
  private int mDayNameLength = LENGTH_SHORT;
  private int mOverlappingEventGap = 0;
  private int mEventMarginVertical = 0;
  private float mXScrollingSpeed = 1f;
  private Calendar mScrollToDay = null;
  private Calendar mSelectedDay = null;
  private boolean mShowDistinctWeekendColor = false;
  private boolean mShowNowLine = false;
  private boolean mShowDistinctPastFutureColor = false;
  private boolean mHorizontalFlingEnabled = true;
  private boolean mVerticalFlingEnabled = true;
  private int mAllDayEventHeight = 0;
  private int mScrollDuration = 250;

  // Listeners.
  private EventClickListener mEventClickListener;
  private EventLongPressListener mEventLongPressListener;
  private DateTimeInterpreter mDateTimeInterpreter;
  private ScrollListener mScrollListener;

  private float mRatio = 1;

  private float DAY_RADIUS = 45;

  // private float mTmpScrollMax;

  private float scrollstartX1;

  private float scrollStartY1;

  private float mDifferenceDelta = 0.0f;

  private final GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() {

    @Override
    public boolean onDown(MotionEvent e) {
      goToNearestOrigin();
      return true;
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
        float distanceY) {
      // Check if view is zoomed.
      if (mIsZooming)
        return true;

      switch (mCurrentScrollDirection) {
      case NONE: {
        // Allow scrolling only in one direction.
        if (Math.abs(distanceX) > Math.abs(distanceY)) {
          if (distanceX > 0) {
            mCurrentScrollDirection = Direction.LEFT;
          } else {
            mCurrentScrollDirection = Direction.RIGHT;
          }
        } else {
          mCurrentScrollDirection = Direction.VERTICAL;
        }
        break;
      }
      case LEFT: {
        // Change direction if there was enough change.
        if (Math.abs(distanceX) > Math.abs(distanceY)
            && (distanceX < -mScaledTouchSlop)) {
          mCurrentScrollDirection = Direction.RIGHT;
        }
        break;
      }
      case RIGHT: {
        // Change direction if there was enough change.
        if (Math.abs(distanceX) > Math.abs(distanceY)
            && (distanceX > mScaledTouchSlop)) {
          mCurrentScrollDirection = Direction.LEFT;
        }
        break;
      }
      }

      // if (scrollstartX1 != e1.getX() || scrollStartY1 != e1.getY()) {
      //
      // scrollstartX1 = e1.getX();
      //
      // scrollStartY1 = e1.getY();
      //
      // switch (mCurrentScrollDirection) {
      // case LEFT:
      // // mFirstVisibleDay.add(Calendar.DATE, 7);
      // //
      // // goToDate(mFirstVisibleDay);
      // //
      // // ViewCompat.postInvalidateOnAnimation(WeekView.this);
      // mTmpScrollMax = mCurrentOrigin.x - (mWidthPerDay + mColumnGap) * 7;
      // break;
      // case RIGHT:
      // mTmpScrollMax = mCurrentOrigin.x + (mWidthPerDay + mColumnGap) * 7;
      // // mFirstVisibleDay.add(Calendar.DATE, -7);
      // //
      // // goToDate(mFirstVisibleDay);
      // //
      // // ViewCompat.postInvalidateOnAnimation(WeekView.this);
      // break;
      // }
      // }

      // // Calculate the new origin after scroll.
      // switch (mCurrentScrollDirection) {
      // case LEFT:
      // mCurrentOrigin.x -= distanceX * mXScrollingSpeed;
      // // if (mCurrentOrigin.x <= mTmpScrollMax)
      // // mCurrentOrigin.x = mTmpScrollMax;
      // ViewCompat.postInvalidateOnAnimation(WeekView.this);
      // break;
      // case RIGHT:
      // mCurrentOrigin.x -= distanceX * mXScrollingSpeed;
      // // if (mCurrentOrigin.x >= mTmpScrollMax)
      // // mCurrentOrigin.x = mTmpScrollMax;
      // ViewCompat.postInvalidateOnAnimation(WeekView.this);
      // break;
      // case VERTICAL:
      // mCurrentOrigin.y -= distanceY;
      // ViewCompat.postInvalidateOnAnimation(WeekView.this);
      // break;
      // }
      return true;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
        float velocityY) {
      if (!mScroller.isFinished())
        return true;

      if (mIsZooming)
        return true;

      if ((mCurrentFlingDirection == Direction.LEFT && !mHorizontalFlingEnabled)
          || (mCurrentFlingDirection == Direction.RIGHT && !mHorizontalFlingEnabled)
          || (mCurrentFlingDirection == Direction.VERTICAL && !mVerticalFlingEnabled)) {
        return true;
      }

      mScroller.forceFinished(true);

      mCurrentFlingDirection = mCurrentScrollDirection;
      switch (mCurrentFlingDirection) {
      case LEFT:
      case RIGHT:
        float stepOver = (mCurrentOrigin.x + (mWidthPerDay + mColumnGap) * 7
            * Math.signum(velocityX));
        boolean scroll = climpFlingRange(stepOver, mCurrentScrollDirection);
        if (!scroll)
          return true;
        mScroller.fling((int) mCurrentOrigin.x, (int) mCurrentOrigin.y,
            (int) (velocityX * mXScrollingSpeed), 0, (int) stepOver,
            (int) stepOver, 0, 0);
        break;
      case VERTICAL:
        // mScroller.fling((int) mCurrentOrigin.x, (int) mCurrentOrigin.y, 0,
        // (int) velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE, 0, 0);
        break;
      }

      ViewCompat.postInvalidateOnAnimation(WeekView.this);
      return true;
    }

    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
      // If the tap was on an event then trigger the callback.
      if (mEventRects != null && mEventClickListener != null) {
        List<EventRect> reversedEventRects = mEventRects;
        Collections.reverse(reversedEventRects);
        for (EventRect event : reversedEventRects) {
          if (event.rectF != null && e.getX() > event.rectF.left
              && e.getX() < event.rectF.right && e.getY() > event.rectF.top
              && e.getY() < event.rectF.bottom) {
            mEventClickListener.onEventClick(event.originalEvent, event.rectF);
            mSelectedDay = event.originalEvent.getStartTime();
            playSoundEffect(SoundEffectConstants.CLICK);
            invalidate();
            return super.onSingleTapConfirmed(e);
          }
        }
      }

      return super.onSingleTapConfirmed(e);
    }

    @Override
    public void onLongPress(MotionEvent e) {
      super.onLongPress(e);

      if (mEventLongPressListener != null && mEventRects != null) {
        List<EventRect> reversedEventRects = mEventRects;
        Collections.reverse(reversedEventRects);
        for (EventRect event : reversedEventRects) {
          if (event.rectF != null && e.getX() > event.rectF.left
              && e.getX() < event.rectF.right && e.getY() > event.rectF.top
              && e.getY() < event.rectF.bottom) {
            mEventLongPressListener.onEventLongPress(event.originalEvent,
                event.rectF);
            performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
            return;
          }
        }
      }

    }
  };

  public WeekView(Context context) {
    this(context, null);
  }

  public WeekView(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public WeekView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    // Hold references.
    mContext = context;

    mRatio = getPixelRatio(mContext);

    DAY_RADIUS = 45 * mRatio;

    mMondayGap = 80 * mRatio;

    mYearMonthGap = 5 * mRatio;

    // Get the attribute values (if any).
    TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
        R.styleable.WeekView, 0, 0);
    try {
      mFirstDayOfWeek = a.getInteger(R.styleable.WeekView_firstDayOfWeek,
          mFirstDayOfWeek);
      mTextSize = a.getDimensionPixelSize(R.styleable.WeekView_textSize,
          (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
              mTextSize, context.getResources().getDisplayMetrics()));
      mHeaderColumnPadding = a.getDimensionPixelSize(
          R.styleable.WeekView_headerColumnPadding, mHeaderColumnPadding);
      mColumnGap = a.getDimensionPixelSize(R.styleable.WeekView_columnGap,
          mColumnGap);
      mHeaderColumnTextColor = a.getColor(
          R.styleable.WeekView_headerColumnTextColor, mHeaderColumnTextColor);
      mNumberOfVisibleDays = a.getInteger(R.styleable.WeekView_noOfVisibleDays,
          mNumberOfVisibleDays);
      mShowFirstDayOfWeekFirst = a.getBoolean(
          R.styleable.WeekView_showFirstDayOfWeekFirst,
          mShowFirstDayOfWeekFirst);
      mHeaderRowPadding = a.getDimensionPixelSize(
          R.styleable.WeekView_headerRowPadding, mHeaderRowPadding);
      mHeaderRowBackgroundColor = a.getColor(
          R.styleable.WeekView_headerRowBackgroundColor,
          mHeaderRowBackgroundColor);
      mDayBackgroundColor = a.getColor(R.styleable.WeekView_dayBackgroundColor,
          mDayBackgroundColor);
      mFutureBackgroundColor = a.getColor(
          R.styleable.WeekView_futureBackgroundColor, mFutureBackgroundColor);
      mPastBackgroundColor = a.getColor(
          R.styleable.WeekView_pastBackgroundColor, mPastBackgroundColor);
      mFutureWeekendBackgroundColor = a.getColor(
          R.styleable.WeekView_futureWeekendBackgroundColor,
          mFutureBackgroundColor); // If not set, use the same color as in the
                                   // week
      mPastWeekendBackgroundColor = a
          .getColor(R.styleable.WeekView_pastWeekendBackgroundColor,
              mPastBackgroundColor);
      mNowLineColor = a.getColor(R.styleable.WeekView_nowLineColor,
          mNowLineColor);
      mNowLineThickness = a.getDimensionPixelSize(
          R.styleable.WeekView_nowLineThickness, mNowLineThickness);
      mHourSeparatorColor = a.getColor(R.styleable.WeekView_hourSeparatorColor,
          mHourSeparatorColor);
      mTodayBackgroundColor = a.getColor(
          R.styleable.WeekView_todayBackgroundColor, mTodayBackgroundColor);
      mHourSeparatorHeight = a.getDimensionPixelSize(
          R.styleable.WeekView_hourSeparatorHeight, mHourSeparatorHeight);
      mTodayHeaderTextColor = a.getColor(
          R.styleable.WeekView_todayHeaderTextColor, mTodayHeaderTextColor);
      mEventTextSize = a.getDimensionPixelSize(
          R.styleable.WeekView_eventTextSize, (int) TypedValue.applyDimension(
              TypedValue.COMPLEX_UNIT_SP, mEventTextSize, context
                  .getResources().getDisplayMetrics()));
      mEventTextColor = a.getColor(R.styleable.WeekView_eventTextColor,
          mEventTextColor);
      mEventPadding = a.getDimensionPixelSize(
          R.styleable.WeekView_eventPadding, mEventPadding);
      mHeaderColumnBackgroundColor = a.getColor(
          R.styleable.WeekView_headerColumnBackground,
          mHeaderColumnBackgroundColor);
      mDayNameLength = a.getInteger(R.styleable.WeekView_dayNameLength,
          mDayNameLength);
      mOverlappingEventGap = a.getDimensionPixelSize(
          R.styleable.WeekView_overlappingEventGap, mOverlappingEventGap);
      mEventMarginVertical = a.getDimensionPixelSize(
          R.styleable.WeekView_eventMarginVertical, mEventMarginVertical);
      mXScrollingSpeed = a.getFloat(R.styleable.WeekView_xScrollingSpeed,
          mXScrollingSpeed);
      mShowDistinctPastFutureColor = a.getBoolean(
          R.styleable.WeekView_showDistinctPastFutureColor,
          mShowDistinctPastFutureColor);
      mShowDistinctWeekendColor = a.getBoolean(
          R.styleable.WeekView_showDistinctWeekendColor,
          mShowDistinctWeekendColor);
      mShowNowLine = a.getBoolean(R.styleable.WeekView_showNowLine,
          mShowNowLine);
      mHorizontalFlingEnabled = a.getBoolean(
          R.styleable.WeekView_horizontalFlingEnabled, mHorizontalFlingEnabled);
      mVerticalFlingEnabled = a.getBoolean(
          R.styleable.WeekView_verticalFlingEnabled, mVerticalFlingEnabled);
      mAllDayEventHeight = a.getDimensionPixelSize(
          R.styleable.WeekView_allDayEventHeight, mAllDayEventHeight);
      mScrollDuration = a.getInt(R.styleable.WeekView_scrollDuration,
          mScrollDuration);
    } finally {
      a.recycle();
    }

    mHeaderRowPadding *= this.mRatio;

    init();
  }

  public float getPixelRatio(Context context) {
    DisplayMetrics dm = context.getResources().getDisplayMetrics();
    int screenWidth = dm.widthPixels;
    int screenHeight = dm.heightPixels;
    float ratioWidth = (float) screenWidth / 1080;
    float ratioHeight = (float) screenHeight / 1920;
    float ratio = Math.min(ratioWidth, ratioHeight);
    return ratio;
  }

  private void init() {
    // Scrolling initialization.
    setSoundEffectsEnabled(true);
    mGestureDetector = new GestureDetectorCompat(mContext, mGestureListener);
    mScroller = new OverScroller(mContext, new LinearInterpolator());

    mMinimumFlingVelocity = ViewConfiguration.get(mContext)
        .getScaledMinimumFlingVelocity();
    mScaledTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();

    // Measure settings for time column.
    mTimeTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mTimeTextPaint.setTextAlign(Paint.Align.RIGHT);
    mTimeTextPaint.setTextSize(mTextSize);
    mTimeTextPaint.setColor(mHeaderColumnTextColor);
    Rect rect = new Rect();
    mTimeTextPaint.getTextBounds("00 PM", 0, "00 PM".length(), rect);
    mTimeTextHeight = rect.height();
    mHeaderMarginBottom = mTimeTextHeight / 2;
    initTextTimeWidth();

    // Measure settings for header row.
    mHeaderTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mHeaderTextPaint.setColor(mHeaderColumnTextColor);
    mHeaderTextPaint.setTextAlign(Paint.Align.CENTER);
    mHeaderTextPaint.setTextSize(mTextSize);
    mHeaderTextPaint.getTextBounds("00 PM", 0, "00 PM".length(), rect);
    mHeaderTextHeight = rect.height();
    mHeaderTextWidth = rect.width();
    mHeaderTextPaint.setTypeface(Typeface.DEFAULT_BOLD);

    // Prepare header background paint.
    mHeaderBackgroundPaint = new Paint();
    mHeaderBackgroundPaint.setColor(mHeaderRowBackgroundColor);

    mCircleBackgroundPaint = new Paint();
    mCircleBackgroundPaint.setColor(Color.rgb(66, 178, 247));
    mCircleBackgroundPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    mCircleBackgroundPaint.setAntiAlias(true);
    mCircleBackgroundPaint.setTextAlign(Paint.Align.CENTER);
    mCircleBackgroundPaint.setTextSize(mTextSize);

    mSelectedBackgroundPaint = new Paint();
    mSelectedBackgroundPaint.setColor(Color.rgb(153, 153, 153));
    mSelectedBackgroundPaint.setStyle(Paint.Style.FILL_AND_STROKE);
    mSelectedBackgroundPaint.setAntiAlias(true);
    mSelectedBackgroundPaint.setTextAlign(Paint.Align.CENTER);
    mSelectedBackgroundPaint.setTextSize(mTextSize);

    // Prepare day background color paint.
    mDayBackgroundPaint = new Paint();
    mDayBackgroundPaint.setColor(mDayBackgroundColor);
    mFutureBackgroundPaint = new Paint();
    mFutureBackgroundPaint.setColor(mFutureBackgroundColor);
    mPastBackgroundPaint = new Paint();
    mPastBackgroundPaint.setColor(mPastBackgroundColor);
    mFutureWeekendBackgroundPaint = new Paint();
    mFutureWeekendBackgroundPaint.setColor(mFutureWeekendBackgroundColor);
    mPastWeekendBackgroundPaint = new Paint();
    mPastWeekendBackgroundPaint.setColor(mPastWeekendBackgroundColor);

    // Prepare hour separator color paint.
    mHourSeparatorPaint = new Paint();
    mHourSeparatorPaint.setStyle(Paint.Style.STROKE);
    mHourSeparatorPaint.setStrokeWidth(mHourSeparatorHeight);
    mHourSeparatorPaint.setColor(mHourSeparatorColor);

    // Prepare the "now" line color paint
    mNowLinePaint = new Paint();
    mNowLinePaint.setStrokeWidth(mNowLineThickness);
    mNowLinePaint.setColor(mNowLineColor);

    // Prepare today background color paint.
    mTodayBackgroundPaint = new Paint();
    mTodayBackgroundPaint.setColor(mTodayBackgroundColor);

    // Prepare today header text color paint.
    mTodayHeaderTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    mTodayHeaderTextPaint.setTextAlign(Paint.Align.CENTER);
    mTodayHeaderTextPaint.setTextSize(mTextSize);
    mTodayHeaderTextPaint.setTypeface(Typeface.DEFAULT_BOLD);
    mTodayHeaderTextPaint.setColor(mTodayHeaderTextColor);

    // Prepare event background color.
    mEventBackgroundPaint = new Paint();
    mEventBackgroundPaint.setColor(Color.rgb(174, 208, 238));

    // Prepare header column background color.
    mHeaderColumnBackgroundPaint = new Paint();
    mHeaderColumnBackgroundPaint.setColor(mHeaderColumnBackgroundColor);

    // Prepare event text size and color.
    mEventTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG
        | Paint.LINEAR_TEXT_FLAG);
    mEventTextPaint.setStyle(Paint.Style.FILL);
    mEventTextPaint.setColor(mEventTextColor);
    mEventTextPaint.setTextSize(mEventTextSize);

    // Set default event color.
    mDefaultEventColor = Color.parseColor("#9fc6e7");

  }

  // fix rotation changes
  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    WIDTH = w;

    HEIGHT = h;

    mAreDimensionsInvalid = true;
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    int desiredWidth = (int) (1000 * this.mRatio);

    int desiredHeight = (int) (800 * this.mRatio);

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);

    int widthSize = MeasureSpec.getSize(widthMeasureSpec);

    int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    int width;

    int height;

    // Measure Width
    if (widthMode == MeasureSpec.EXACTLY) {
      // Must be this size
      width = widthSize;
    } else if (widthMode == MeasureSpec.AT_MOST) {
      // Can't be bigger than...
      width = Math.min(desiredWidth, widthSize);
    } else {
      // Be whatever you want
      width = desiredWidth;
    }

    // Measure Height
    if (heightMode == MeasureSpec.EXACTLY) {
      // Must be this size
      height = heightSize;
    } else if (heightMode == MeasureSpec.AT_MOST) {
      // Can't be bigger than...
      height = Math.min(desiredHeight, heightSize);
    } else {
      // Be whatever you want
      height = desiredHeight;
    }

    // MUST CALL THIS
    setMeasuredDimension(width, height);
  }

  /**
   * Initialize time column width. Calculate value with all possible hours
   * (supposed widest text).
   */
  private void initTextTimeWidth() {
    mTimeTextWidth = 0;
    for (int i = 0; i < 24; i++) {
      // Measure time string and get max width.
      String time = getDateTimeInterpreter().interpretTime(i);
      if (time == null)
        throw new IllegalStateException(
            "A DateTimeInterpreter must not return null time");
      mTimeTextWidth = Math.max(mTimeTextWidth,
          mTimeTextPaint.measureText(time));
    }
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    // Draw the header row.
    drawHeaderRowAndEvents(canvas);
  }

  private boolean containsDay(List<EventRect> eRs, Calendar dayTwo) {
    for (EventRect er : eRs) {
      boolean same = WeekViewUtil.isSameDay(er.originalEvent.getStartTime(),
          dayTwo);
      if (same)
        return same;
    }
    return false;
  }

  private void drawHeaderRowAndEvents(Canvas canvas) {
    // Calculate the available width for each day.
    mHeaderColumnWidth = 0;// mTimeTextWidth + mHeaderColumnPadding * 2;
    mWidthPerDay = getWidth() - mHeaderColumnWidth - mColumnGap
        * (mNumberOfVisibleDays - 1);
    mWidthPerDay = mWidthPerDay / mNumberOfVisibleDays;

    mHeaderHeight = HEIGHT - mHeaderRowPadding * 2 + mHeaderMarginBottom;// mHeaderTextHeight
                                                                         // +
    // (mAllDayEventHeight +
    // mHeaderMarginBottom);

    Calendar today = WeekViewUtil.today();

    if (mAreDimensionsInvalid) {
      mAreDimensionsInvalid = false;
      if (mScrollToDay != null)
        goToDate(mScrollToDay);

      mAreDimensionsInvalid = false;

      mScrollToDay = null;
      mAreDimensionsInvalid = false;
    }
    if (mIsFirstDraw) {
      mIsFirstDraw = false;

      // If the week view is being drawn for the first time, then consider the
      // first day of the week.
      if (mNumberOfVisibleDays >= 7
          && today.get(Calendar.DAY_OF_WEEK) != mFirstDayOfWeek
          && mShowFirstDayOfWeekFirst) {
        int difference = (today.get(Calendar.DAY_OF_WEEK) - mFirstDayOfWeek);
        mDifferenceDelta = (mWidthPerDay + mColumnGap) * difference;
        mCurrentOrigin.x += mDifferenceDelta;
      }
      setScrollRange(mCurrentOrigin.x, today.get(Calendar.DAY_OF_WEEK));
    }

    // Don't put an "else if" because it will trigger a glitch when completely
    // zoomed out and
    // scrolling vertically.
    if (mCurrentOrigin.y > 0) {
      mCurrentOrigin.y = 0;
    }

    // Consider scroll offset.
    int leftDaysWithGaps = (int) -(Math.ceil(mCurrentOrigin.x
        / (mWidthPerDay + mColumnGap)));
    float startFromPixel = mCurrentOrigin.x + (mWidthPerDay + mColumnGap)
        * leftDaysWithGaps + mHeaderColumnWidth;
    float startPixel = startFromPixel;

    // Prepare to iterate for each day.
    Calendar day = (Calendar) today.clone();
    day.add(Calendar.HOUR, 6);

    // Clear the cache for event rectangles.
    if (mEventRects != null) {
      mEventRects.clear();
      mEventRects = null;
      mAreWeekHeaderEventInvalid = true;
    }

    // Clip to paint events only.
    // canvas.clipRect(mHeaderColumnWidth, mHeaderHeight + mHeaderRowPadding * 2
    // + mHeaderMarginBottom + mTimeTextHeight / 2, getWidth(), getHeight(),
    // Region.Op.REPLACE);

    // Iterate through each day.
    Calendar oldFirstVisibleDay = mFirstVisibleDay;
    mFirstVisibleDay = (Calendar) today.clone();
    mFirstVisibleDay.add(Calendar.DATE,
        -(Math.round(mCurrentOrigin.x / (mWidthPerDay + mColumnGap))));
    if (!mFirstVisibleDay.equals(oldFirstVisibleDay) && mScrollListener != null) {
      mScrollListener.onFirstVisibleDayChanged(mFirstVisibleDay,
          oldFirstVisibleDay);
    }

    // Clip to paint header row only.
    // canvas.clipRect(mHeaderColumnWidth, 0, getWidth(), mHeaderHeight
    // + mHeaderRowPadding * 2, Region.Op.REPLACE);

    // Draw the header background.
    canvas.drawRect(0, 0, getWidth(), mHeaderHeight + mHeaderRowPadding * 2,
        mHeaderBackgroundPaint);

    // Draw the header row texts.
    startPixel = startFromPixel;

    if (mAreWeekHeaderEventInvalid) {
      mAreWeekHeaderEventInvalid = false;
      if (mEventRects == null)
        mEventRects = new ArrayList<EventRect>();
    }

    for (int dayNumber = leftDaysWithGaps + 1; dayNumber <= leftDaysWithGaps
        + mNumberOfVisibleDays + 1; dayNumber++) {
      // Check if the day is today.
      day = (Calendar) today.clone();
      day.add(Calendar.DATE, dayNumber - 1);
      boolean sameDay = WeekViewUtil.isSameDay(day, today);

      boolean selectedDay = false;
      if (mSelectedDay != null)
        selectedDay = WeekViewUtil.isSameDay(day, mSelectedDay);

      // Draw the day labels.
      DateBundle dayLabel = getDateTimeInterpreter().interpretDate(day);
      if (dayLabel == null)
        throw new IllegalStateException(
            "A DateTimeInterpreter must not return null date");

      canvas.drawText(dayLabel.WeekDay, startPixel + mWidthPerDay / 2,
          mHeaderTextHeight + mHeaderRowPadding, mHeaderTextPaint);

      if (sameDay || selectedDay)
        canvas.drawCircle(startPixel + mWidthPerDay / 2, mHeaderTextHeight
            + mHeaderRowPadding + mMondayGap - mHeaderTextHeight / 2,
            DAY_RADIUS, sameDay ? mCircleBackgroundPaint
                : selectedDay ? mSelectedBackgroundPaint
                    : mSelectedBackgroundPaint);

      canvas.drawText(dayLabel.MonthDay, startPixel + mWidthPerDay / 2,
          mHeaderTextHeight + mHeaderRowPadding + mMondayGap, sameDay
              || selectedDay ? mTodayHeaderTextPaint : mHeaderTextPaint);

      if (dayNumber == leftDaysWithGaps + 1)
        canvas.drawText(dayLabel.YearMonth, mWidthPerDay / 2 + mHeaderTextWidth
            / 2, mHeaderRowPadding - mHeaderTextHeight / 2 - mYearMonthGap,
            mCircleBackgroundPaint);
      if (mEventRects != null && !containsDay(mEventRects, day)) {
        float left = startPixel;
        float top = 0;
        float right = startPixel + mWidthPerDay;
        float bottom = mHeaderHeight + mHeaderRowPadding * 2;
        RectF rectF = new RectF(left, top, right, bottom);
        SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd");
        mEventRects
            .add(new EventRect(day, rectF, format1.format(day.getTime())));
      }

      // drawAllDayEvents(day, startPixel, canvas);
      startPixel += mWidthPerDay + mColumnGap;

    }

  }

  private float mFlingXMin = 0.0f;

  private float mFlingXMax = 0.0f;

  /**
   * @param x
   */
  private void setScrollRange(float x, int dayOfTheWEEK) {
    // TODO Auto-generated method stub
    switch (dayOfTheWEEK) {
    case Calendar.MONDAY:
    case Calendar.TUESDAY:
    case Calendar.WEDNESDAY:
    case Calendar.THURSDAY:
    case Calendar.FRIDAY:
      mFlingXMin = x - (mWidthPerDay + mColumnGap) * 7 * 3;
      mFlingXMax = x + (mWidthPerDay + mColumnGap) * 7 * 3;
      break;
    case Calendar.SATURDAY:
      mFlingXMin = x - (mWidthPerDay + mColumnGap) * 7 * 2;
      mFlingXMax = x + (mWidthPerDay + mColumnGap) * 7 * 3;
      break;
    case Calendar.SUNDAY:
      mFlingXMin = x - (mWidthPerDay + mColumnGap) * 7 * 3;
      mFlingXMax = x + (mWidthPerDay + mColumnGap) * 7 * 2;
      break;
    }
  }

  private boolean climpFlingRange(float x, Direction d) {
    // TODO Auto-generated method stub
    x -= mDifferenceDelta;
    if (x <= mFlingXMin - 2 && d == Direction.LEFT) {
      mScroller.forceFinished(true);
      return false;
    } else if (x >= mFlingXMax + 2 && d == Direction.RIGHT) {
      mScroller.forceFinished(true);
      return false;
    } else {
      return true;
    }
  }

  /**
   * A class to hold reference to the events and their visual representation. An
   * EventRect is actually the rectangle that is drawn on the calendar for a
   * given event. There may be more than one rectangle for a single event (an
   * event that expands more than one day). In that case two instances of the
   * EventRect will be used for a single event. The given event will be stored
   * in "originalEvent". But the event that corresponds to rectangle the
   * rectangle instance will be stored in "event".
   */
  private class EventRect {
    public WeekViewEvent event;
    public WeekViewEvent originalEvent;
    public RectF rectF;
    public float left;
    public float width;
    public float top;
    public float bottom;

    /**
     * Create a new instance of event rect. An EventRect is actually the
     * rectangle that is drawn on the calendar for a given event. There may be
     * more than one rectangle for a single event (an event that expands more
     * than one day). In that case two instances of the EventRect will be used
     * for a single event. The given event will be stored in "originalEvent".
     * But the event that corresponds to rectangle the rectangle instance will
     * be stored in "event".
     * 
     * @param event
     *          Represents the event which this instance of rectangle
     *          represents.
     * @param originalEvent
     *          The original event that was passed by the user.
     * @param rectF
     *          The rectangle.
     */
    public EventRect(WeekViewEvent event, WeekViewEvent originalEvent,
        RectF rectF) {
      this.event = event;
      this.rectF = rectF;
      this.originalEvent = originalEvent;
    }

    public EventRect(Calendar day, RectF rf, String name) {
      this.rectF = rf;
      this.left = rf.left;
      this.top = rf.top;
      this.width = rf.right - rf.left;
      this.bottom = rf.bottom;
      this.event = new WeekViewEvent(day, name);
      this.originalEvent = new WeekViewEvent(day, name);
    }
  }

  @Override
  public void invalidate() {
    super.invalidate();
    mAreDimensionsInvalid = true;
  }

  // ///////////////////////////////////////////////////////////////
  //
  // Functions related to setting and getting the properties.
  //
  // ///////////////////////////////////////////////////////////////

  public void setOnEventClickListener(EventClickListener listener) {
    this.mEventClickListener = listener;
  }

  public EventClickListener getEventClickListener() {
    return mEventClickListener;
  }

  public EventLongPressListener getEventLongPressListener() {
    return mEventLongPressListener;
  }

  public void setEventLongPressListener(
      EventLongPressListener eventLongPressListener) {
    this.mEventLongPressListener = eventLongPressListener;
  }

  public void setScrollListener(ScrollListener scrolledListener) {
    this.mScrollListener = scrolledListener;
  }

  public ScrollListener getScrollListener() {
    return mScrollListener;
  }

  /**
   * Get the interpreter which provides the text to show in the header column
   * and the header row.
   * 
   * @return The date, time interpreter.
   */
  public DateTimeInterpreter getDateTimeInterpreter() {
    if (mDateTimeInterpreter == null) {
      mDateTimeInterpreter = new DateTimeInterpreter() {
        @Override
        public DateBundle interpretDate(Calendar date) {
          try {
            // SimpleDateFormat sdf = mDayNameLength == LENGTH_SHORT ? new
            // SimpleDateFormat(
            // "E", Locale.getDefault()) : new SimpleDateFormat("EEE M/dd",
            // Locale.getDefault());
            // return sdf.format(date.getTime()).toUpperCase();

            String[] weeks = { "日", "一", "二", "三", "四", "五", "六" };
            // date.setTime(date);
            int week_index = date.get(Calendar.DAY_OF_WEEK) - 1;

            if (week_index < 0) {
              week_index = 0;
            }

            int dayOfMonth = date.get(Calendar.DAY_OF_MONTH);

            String dayOfMonthStr = String.valueOf(dayOfMonth);

            DateBundle db = new DateBundle();

            db.WeekDay = weeks[week_index];

            db.MonthDay = dayOfMonthStr;

            db.YearMonth = date.get(Calendar.YEAR) + "."
                + (date.get(Calendar.MONTH) + 1);

            return db;

          } catch (Exception e) {
            e.printStackTrace();
            DateBundle db = new DateBundle();
            db.MonthDay = "EXCEPTION_MONTHDAY";
            db.WeekDay = "EXCEPTION_WEEKDAY";
            db.YearMonth = "EXCEPTION_YEAR_MONTH";
            return db;
          }
        }

        @Override
        public String interpretTime(int hour) {
          Calendar calendar = Calendar.getInstance();
          calendar.set(Calendar.HOUR_OF_DAY, hour);
          calendar.set(Calendar.MINUTE, 0);

          try {
            SimpleDateFormat sdf = DateFormat.is24HourFormat(getContext()) ? new SimpleDateFormat(
                "HH:mm", Locale.getDefault()) : new SimpleDateFormat("hh a",
                Locale.getDefault());
            return sdf.format(calendar.getTime());
          } catch (Exception e) {
            e.printStackTrace();
            return "";
          }
        }
      };
    }
    return mDateTimeInterpreter;
  }

  /**
   * Set the interpreter which provides the text to show in the header column
   * and the header row.
   * 
   * @param dateTimeInterpreter
   *          The date, time interpreter.
   */
  public void setDateTimeInterpreter(DateTimeInterpreter dateTimeInterpreter) {
    this.mDateTimeInterpreter = dateTimeInterpreter;

    // Refresh time column width.
    initTextTimeWidth();
  }

  /**
   * Get the number of visible days in a week.
   * 
   * @return The number of visible days in a week.
   */
  public int getNumberOfVisibleDays() {
    return mNumberOfVisibleDays;
  }

  /**
   * Set the number of visible days in a week.
   * 
   * @param numberOfVisibleDays
   *          The number of visible days in a week.
   */
  public void setNumberOfVisibleDays(int numberOfVisibleDays) {
    this.mNumberOfVisibleDays = numberOfVisibleDays;
    mCurrentOrigin.x = 0;
    mCurrentOrigin.y = 0;
    invalidate();
  }

  public int getColumnGap() {
    return mColumnGap;
  }

  public void setColumnGap(int columnGap) {
    mColumnGap = columnGap;
    invalidate();
  }

  public int getFirstDayOfWeek() {
    return mFirstDayOfWeek;
  }

  /**
   * Set the first day of the week. First day of the week is used only when the
   * week view is first drawn. It does not of any effect after user starts
   * scrolling horizontally.
   * <p>
   * <b>Note:</b> This method will only work if the week view is set to display
   * more than 6 days at once.
   * </p>
   * 
   * @param firstDayOfWeek
   *          The supported values are {@link Calendar#SUNDAY},
   *          {@link Calendar#MONDAY},
   *          {@link Calendar#TUESDAY},
   *          {@link Calendar#WEDNESDAY},
   *          {@link Calendar#THURSDAY},
   *          {@link Calendar#FRIDAY}.
   */
  public void setFirstDayOfWeek(int firstDayOfWeek) {
    mFirstDayOfWeek = firstDayOfWeek;
    invalidate();
  }

  public boolean isShowFirstDayOfWeekFirst() {
    return mShowFirstDayOfWeekFirst;
  }

  public void setShowFirstDayOfWeekFirst(boolean show) {
    mShowFirstDayOfWeekFirst = show;
  }

  public int getTextSize() {
    return mTextSize;
  }

  public void setTextSize(int textSize) {
    mTextSize = textSize;
    mTodayHeaderTextPaint.setTextSize(mTextSize);
    mHeaderTextPaint.setTextSize(mTextSize);
    mTimeTextPaint.setTextSize(mTextSize);
    invalidate();
  }

  public int getHeaderColumnPadding() {
    return mHeaderColumnPadding;
  }

  public void setHeaderColumnPadding(int headerColumnPadding) {
    mHeaderColumnPadding = headerColumnPadding;
    invalidate();
  }

  public int getHeaderColumnTextColor() {
    return mHeaderColumnTextColor;
  }

  public void setHeaderColumnTextColor(int headerColumnTextColor) {
    mHeaderColumnTextColor = headerColumnTextColor;
    mHeaderTextPaint.setColor(mHeaderColumnTextColor);
    mTimeTextPaint.setColor(mHeaderColumnTextColor);
    invalidate();
  }

  public int getHeaderRowPadding() {
    return mHeaderRowPadding;
  }

  public void setHeaderRowPadding(int headerRowPadding) {
    mHeaderRowPadding = headerRowPadding;
    invalidate();
  }

  public int getHeaderRowBackgroundColor() {
    return mHeaderRowBackgroundColor;
  }

  public void setHeaderRowBackgroundColor(int headerRowBackgroundColor) {
    mHeaderRowBackgroundColor = headerRowBackgroundColor;
    mHeaderBackgroundPaint.setColor(mHeaderRowBackgroundColor);
    invalidate();
  }

  public int getDayBackgroundColor() {
    return mDayBackgroundColor;
  }

  public void setDayBackgroundColor(int dayBackgroundColor) {
    mDayBackgroundColor = dayBackgroundColor;
    mDayBackgroundPaint.setColor(mDayBackgroundColor);
    invalidate();
  }

  public int getHourSeparatorColor() {
    return mHourSeparatorColor;
  }

  public void setHourSeparatorColor(int hourSeparatorColor) {
    mHourSeparatorColor = hourSeparatorColor;
    mHourSeparatorPaint.setColor(mHourSeparatorColor);
    invalidate();
  }

  public int getTodayBackgroundColor() {
    return mTodayBackgroundColor;
  }

  public void setTodayBackgroundColor(int todayBackgroundColor) {
    mTodayBackgroundColor = todayBackgroundColor;
    mTodayBackgroundPaint.setColor(mTodayBackgroundColor);
    invalidate();
  }

  public int getHourSeparatorHeight() {
    return mHourSeparatorHeight;
  }

  public void setHourSeparatorHeight(int hourSeparatorHeight) {
    mHourSeparatorHeight = hourSeparatorHeight;
    mHourSeparatorPaint.setStrokeWidth(mHourSeparatorHeight);
    invalidate();
  }

  public int getTodayHeaderTextColor() {
    return mTodayHeaderTextColor;
  }

  public void setTodayHeaderTextColor(int todayHeaderTextColor) {
    mTodayHeaderTextColor = todayHeaderTextColor;
    mTodayHeaderTextPaint.setColor(mTodayHeaderTextColor);
    invalidate();
  }

  public int getEventTextSize() {
    return mEventTextSize;
  }

  public void setEventTextSize(int eventTextSize) {
    mEventTextSize = eventTextSize;
    mEventTextPaint.setTextSize(mEventTextSize);
    invalidate();
  }

  public int getEventTextColor() {
    return mEventTextColor;
  }

  public void setEventTextColor(int eventTextColor) {
    mEventTextColor = eventTextColor;
    mEventTextPaint.setColor(mEventTextColor);
    invalidate();
  }

  public int getEventPadding() {
    return mEventPadding;
  }

  public void setEventPadding(int eventPadding) {
    mEventPadding = eventPadding;
    invalidate();
  }

  public int getHeaderColumnBackgroundColor() {
    return mHeaderColumnBackgroundColor;
  }

  public void setHeaderColumnBackgroundColor(int headerColumnBackgroundColor) {
    mHeaderColumnBackgroundColor = headerColumnBackgroundColor;
    mHeaderColumnBackgroundPaint.setColor(mHeaderColumnBackgroundColor);
    invalidate();
  }

  public int getDefaultEventColor() {
    return mDefaultEventColor;
  }

  public void setDefaultEventColor(int defaultEventColor) {
    mDefaultEventColor = defaultEventColor;
    invalidate();
  }

  /**
   * <b>Note:</b> Use {@link #setDateTimeInterpreter(DateTimeInterpreter)} and
   * {@link #getDateTimeInterpreter()} instead.
   * 
   * @return Either long or short day name is being used.
   */
  @Deprecated
  public int getDayNameLength() {
    return mDayNameLength;
  }

  /**
   * Set the length of the day name displayed in the header row. Example of
   * short day names is 'M' for 'Monday' and example of long day names is 'Mon'
   * for 'Monday'.
   * <p>
   * <b>Note:</b> Use {@link #setDateTimeInterpreter(DateTimeInterpreter)}
   * instead.
   * </p>
   * 
   * @param length
   */
  @Deprecated
  public void setDayNameLength(int length) {
    if (length != LENGTH_LONG && length != LENGTH_SHORT) {
      throw new IllegalArgumentException(
          "length parameter must be either LENGTH_LONG or LENGTH_SHORT");
    }
    this.mDayNameLength = length;
  }

  public int getOverlappingEventGap() {
    return mOverlappingEventGap;
  }

  /**
   * Set the gap between overlapping events.
   * 
   * @param overlappingEventGap
   *          The gap between overlapping events.
   */
  public void setOverlappingEventGap(int overlappingEventGap) {
    this.mOverlappingEventGap = overlappingEventGap;
    invalidate();
  }

  public int getEventMarginVertical() {
    return mEventMarginVertical;
  }

  /**
   * Set the top and bottom margin of the event. The event will release this
   * margin from the top and bottom edge. This margin is useful for
   * differentiation consecutive events.
   * 
   * @param eventMarginVertical
   *          The top and bottom margin.
   */
  public void setEventMarginVertical(int eventMarginVertical) {
    this.mEventMarginVertical = eventMarginVertical;
    invalidate();
  }

  /**
   * Returns the first visible day in the week view.
   * 
   * @return The first visible day in the week view.
   */
  public Calendar getFirstVisibleDay() {
    return mFirstVisibleDay;
  }

  /**
   * Returns the last visible day in the week view.
   * 
   * @return The last visible day in the week view.
   */
  public Calendar getLastVisibleDay() {
    return mLastVisibleDay;
  }

  /**
   * Get the scrolling speed factor in horizontal direction.
   * 
   * @return The speed factor in horizontal direction.
   */
  public float getXScrollingSpeed() {
    return mXScrollingSpeed;
  }

  /**
   * Sets the speed for horizontal scrolling.
   * 
   * @param xScrollingSpeed
   *          The new horizontal scrolling speed.
   */
  public void setXScrollingSpeed(float xScrollingSpeed) {
    this.mXScrollingSpeed = xScrollingSpeed;
  }

  /**
   * Whether weekends should have a background color different from the normal
   * day background color. The weekend background colors are defined by the
   * attributes `futureWeekendBackgroundColor` and `pastWeekendBackgroundColor`.
   * 
   * @return True if weekends should have different background colors.
   */
  public boolean isShowDistinctWeekendColor() {
    return mShowDistinctWeekendColor;
  }

  /**
   * Set whether weekends should have a background color different from the
   * normal day background color. The weekend background colors are defined by
   * the attributes `futureWeekendBackgroundColor` and
   * `pastWeekendBackgroundColor`.
   * 
   * @param showDistinctWeekendColor
   *          True if weekends should have different background colors.
   */
  public void setShowDistinctWeekendColor(boolean showDistinctWeekendColor) {
    this.mShowDistinctWeekendColor = showDistinctWeekendColor;
    invalidate();
  }

  /**
   * Whether past and future days should have two different background colors.
   * The past and future day colors are defined by the attributes
   * `futureBackgroundColor` and `pastBackgroundColor`.
   * 
   * @return True if past and future days should have two different background
   *         colors.
   */
  public boolean isShowDistinctPastFutureColor() {
    return mShowDistinctPastFutureColor;
  }

  /**
   * Set whether weekends should have a background color different from the
   * normal day background color. The past and future day colors are defined by
   * the attributes `futureBackgroundColor` and `pastBackgroundColor`.
   * 
   * @param showDistinctPastFutureColor
   *          True if past and future should have two different background
   *          colors.
   */
  public void setShowDistinctPastFutureColor(boolean showDistinctPastFutureColor) {
    this.mShowDistinctPastFutureColor = showDistinctPastFutureColor;
    invalidate();
  }

  /**
   * Get whether "now" line should be displayed. "Now" line is defined by the
   * attributes `nowLineColor` and `nowLineThickness`.
   * 
   * @return True if "now" line should be displayed.
   */
  public boolean isShowNowLine() {
    return mShowNowLine;
  }

  /**
   * Set whether "now" line should be displayed. "Now" line is defined by the
   * attributes `nowLineColor` and `nowLineThickness`.
   * 
   * @param showNowLine
   *          True if "now" line should be displayed.
   */
  public void setShowNowLine(boolean showNowLine) {
    this.mShowNowLine = showNowLine;
    invalidate();
  }

  /**
   * Get the "now" line color.
   * 
   * @return The color of the "now" line.
   */
  public int getNowLineColor() {
    return mNowLineColor;
  }

  /**
   * Set the "now" line color.
   * 
   * @param nowLineColor
   *          The color of the "now" line.
   */
  public void setNowLineColor(int nowLineColor) {
    this.mNowLineColor = nowLineColor;
    invalidate();
  }

  /**
   * Get the "now" line thickness.
   * 
   * @return The thickness of the "now" line.
   */
  public int getNowLineThickness() {
    return mNowLineThickness;
  }

  /**
   * Set the "now" line thickness.
   * 
   * @param nowLineThickness
   *          The thickness of the "now" line.
   */
  public void setNowLineThickness(int nowLineThickness) {
    this.mNowLineThickness = nowLineThickness;
    invalidate();
  }

  /**
   * Get whether the week view should fling horizontally.
   * 
   * @return True if the week view has horizontal fling enabled.
   */
  public boolean isHorizontalFlingEnabled() {
    return mHorizontalFlingEnabled;
  }

  /**
   * Set whether the week view should fling horizontally.
   * 
   * @return True if it should have horizontal fling enabled.
   */
  public void setHorizontalFlingEnabled(boolean enabled) {
    mHorizontalFlingEnabled = enabled;
  }

  /**
   * Get whether the week view should fling vertically.
   * 
   * @return True if the week view has vertical fling enabled.
   */
  public boolean isVerticalFlingEnabled() {
    return mVerticalFlingEnabled;
  }

  /**
   * Set whether the week view should fling vertically.
   * 
   * @return True if it should have vertical fling enabled.
   */
  public void setVerticalFlingEnabled(boolean enabled) {
    mVerticalFlingEnabled = enabled;
  }

  /**
   * Get the height of AllDay-events.
   * 
   * @return Height of AllDay-events.
   */
  public int getAllDayEventHeight() {
    return mAllDayEventHeight;
  }

  /**
   * Set the height of AllDay-events.
   */
  public void setAllDayEventHeight(int height) {
    mAllDayEventHeight = height;
  }

  /**
   * Get scroll duration
   * 
   * @return scroll duration
   */
  public int getScrollDuration() {
    return mScrollDuration;
  }

  /**
   * Set the scroll duration
   */
  public void setScrollDuration(int scrollDuration) {
    mScrollDuration = scrollDuration;
  }

  // ///////////////////////////////////////////////////////////////
  //
  // Functions related to scrolling.
  //
  // ///////////////////////////////////////////////////////////////

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    boolean val = mGestureDetector.onTouchEvent(event);

    // Check after call of mGestureDetector, so mCurrentFlingDirection and
    // mCurrentScrollDirection are set.
    // if (event.getAction() == MotionEvent.ACTION_UP && !mIsZooming
    // && mCurrentFlingDirection == Direction.NONE) {
    // if (mCurrentScrollDirection == Direction.RIGHT
    // || mCurrentScrollDirection == Direction.LEFT) {
    // goToNearestOrigin();
    // }
    // mCurrentScrollDirection = Direction.NONE;
    // }
    if (event.getAction() == MotionEvent.ACTION_MOVE) {
      return false;
    }

    return val;
  }

  private void goToNearestOrigin() {
    double leftDays = mCurrentOrigin.x / (mWidthPerDay + mColumnGap);

    if (mCurrentFlingDirection != Direction.NONE) {
      // snap to nearest day
      leftDays = Math.round(leftDays);
    } else if (mCurrentScrollDirection == Direction.LEFT) {
      // snap to last day
      leftDays = Math.floor(leftDays);
    } else if (mCurrentScrollDirection == Direction.RIGHT) {
      // snap to next day
      leftDays = Math.ceil(leftDays);
    } else {
      // snap to nearest day
      leftDays = Math.round(leftDays);
    }

    int nearestOrigin = (int) (mCurrentOrigin.x - leftDays
        * (mWidthPerDay + mColumnGap));

    if (nearestOrigin != 0) {
      // Stop current animation.
      // mScroller.forceFinished(true);
      // Snap to date.
      // mScroller.startScroll((int) mCurrentOrigin.x, (int) mCurrentOrigin.y,
      // -nearestOrigin, 0, 0);
      ViewCompat.postInvalidateOnAnimation(WeekView.this);
    }
    // Reset scrolling and fling direction.
    mCurrentScrollDirection = mCurrentFlingDirection = Direction.NONE;
  }

  @Override
  public void computeScroll() {
    super.computeScroll();

    if (mScroller.isFinished()) {
      if (mCurrentFlingDirection != Direction.NONE) {
        // Snap to day after fling is finished.
        goToNearestOrigin();
      }
    } else {
      if (mCurrentFlingDirection != Direction.NONE && forceFinishScroll()) {
        goToNearestOrigin();
      } else if (mScroller.computeScrollOffset()) {
        mCurrentOrigin.y = mScroller.getCurrY();
        mCurrentOrigin.x = mScroller.getCurrX();
        ViewCompat.postInvalidateOnAnimation(this);
      }
    }
  }

  /**
   * Check if scrolling should be stopped.
   * 
   * @return true if scrolling should be stopped before reaching the end of
   *         animation.
   */
  private boolean forceFinishScroll() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
      // current velocity only available since api 14
      return mScroller.getCurrVelocity() <= mMinimumFlingVelocity;
    } else {
      return false;
    }
  }

  // ///////////////////////////////////////////////////////////////
  //
  // Public methods.
  //
  // ///////////////////////////////////////////////////////////////

  // /**
  // * Show today on the week view.
  // */
  // public void goToToday() {
  // Calendar today = Calendar.getInstance();
  // goToDate(today);
  // }

  /**
   * Show a specific day on the week view.
   * 
   * @param date
   *          The date to show.
   */
  public void goToDate(Calendar date) {
    mIsFirstDraw = true;
    mScroller.forceFinished(true);
    mCurrentScrollDirection = mCurrentFlingDirection = Direction.NONE;

    date.set(Calendar.HOUR_OF_DAY, 0);
    date.set(Calendar.MINUTE, 0);
    date.set(Calendar.SECOND, 0);
    date.set(Calendar.MILLISECOND, 0);

    if (mAreDimensionsInvalid) {
      mScrollToDay = date;
      return;
    }

    mRefreshEvents = true;

    Calendar today = Calendar.getInstance();
    today.set(Calendar.HOUR_OF_DAY, 0);
    today.set(Calendar.MINUTE, 0);
    today.set(Calendar.SECOND, 0);
    today.set(Calendar.MILLISECOND, 0);

    long day = 1000L * 60L * 60L * 24L;
    long dateInMillis = date.getTimeInMillis()
        + date.getTimeZone().getOffset(date.getTimeInMillis());
    long todayInMillis = today.getTimeInMillis()
        + today.getTimeZone().getOffset(today.getTimeInMillis());
    long dateDifference = (dateInMillis / day) - (todayInMillis / day);
    mCurrentOrigin.x = -dateDifference * (mWidthPerDay + mColumnGap);
    invalidate();
  }

  /**
   * Refreshes the view and loads the events again.
   */
  public void notifyDatasetChanged() {
    mRefreshEvents = true;
    invalidate();
  }

  // ///////////////////////////////////////////////////////////////
  //
  // Interfaces.
  //
  // ///////////////////////////////////////////////////////////////

  public interface EventClickListener {
    /**
     * Triggered when clicked on one existing event
     * 
     * @param event
     *          : event clicked.
     * @param eventRect
     *          : view containing the clicked event.
     */
    void onEventClick(WeekViewEvent event, RectF eventRect);
  }

  public interface EventLongPressListener {
    /**
     * with a long press.
     * 
     * @param event
     *          : event clicked.
     * @param eventRect
     *          : view containing the clicked event.
     */
    void onEventLongPress(WeekViewEvent event, RectF eventRect);
  }

  public interface ScrollListener {
    /**
     * Called when the first visible day has changed.
     * 
     * (this will also be called during the first draw of the weekview)
     * 
     * @param newFirstVisibleDay
     *          The new first visible day
     * @param oldFirstVisibleDay
     *          The old first visible day (is null on the first call).
     */
    void onFirstVisibleDayChanged(Calendar newFirstVisibleDay,
                                  Calendar oldFirstVisibleDay);
  }
}
